package org.jeecg.util;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.Model;
import org.activiti.engine.repository.ModelQuery;
import org.activiti.engine.repository.ProcessDefinition;
import org.apache.commons.lang.StringUtils;
import org.jeecg.common.constant.enums.ActModelNodeEnum;
import org.jeecg.common.domain.req.NewModel;
import org.jeecg.common.domain.res.ProcessDefinitionData;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.modules.activiti.entity.ActZprocess;
import org.jeecg.modules.activiti.service.Impl.ActZprocessServiceImpl;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * activiti 工具包
 *
 * @author luozhiwei
 * @date 2020/11/17 18:42
 */
@Component
@Slf4j
public class ActUtil {

    @Resource
    private RepositoryService repositoryService;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private ActZprocessServiceImpl actZprocessService;


    /**
     * 创建模型
     *
     * @param newModel
     * @return
     */
    public Boolean createModel(NewModel newModel) {
        ModelQuery modelQuery = repositoryService.createModelQuery();
        long count = modelQuery.modelKey(newModel.getKey()).count();
        if (count > 0) {
            throw new JeecgBootException("模型标示已存在，请重新输入");
        }
        try {
            //构建metaInfo信息。 此字段映射文件信息
            ObjectNode modelNode = objectMapper.createObjectNode();
            modelNode.put(ActModelNodeEnum.MODEL_NAME.getContent(), newModel.getName());
            modelNode.put(ActModelNodeEnum.MODEL_DESCRIPTION.getContent(), newModel.getDes());
            modelNode.put(ActModelNodeEnum.MODEL_REVISION.getContent(), "1");

            //构建流程模型
            Model model = repositoryService.newModel();
            model.setName(newModel.getName());
            model.setKey(newModel.getKey());
            model.setCategory(newModel.getCategory());
            model.setMetaInfo(modelNode.toString());

            //保存流程模型入库
            repositoryService.saveModel(model);
            String id = model.getId();

            //完善ModelEditorSource
            ObjectNode editorNode = objectMapper.createObjectNode();
            editorNode.put("id", "canvas");
            editorNode.put("resourceId", "canvas");

            //构建stencilSetNode对象信息
            ObjectNode stencilSetNode = objectMapper.createObjectNode();
            stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");

            //添加stencilSetNode到editorNode
            editorNode.putPOJO("stencilset", stencilSetNode);

            //保存editorSource
            repositoryService.addModelEditorSource(id, editorNode.toString().getBytes(StandardCharsets.UTF_8));

            log.info("工作流模型创建成功 id:{}", id);

            log.info("流程操作 ActivitiUtil ----> createModel 结束");

            return true;
        } catch (Exception e) {
            log.error("工作流模型创建失败 error:{}", e);

            log.info("流程操作 ActivitiUtil ----> createModel 结束");

            return false;
        }
    }

    /**
     * 获取模型分页数据
     *
     * @param pageNo
     * @param pageSize
     * @param parameterMap
     * @return
     */
    public Page<Model> getModelPage(Integer pageNo, Integer pageSize, Map<String, String[]> parameterMap) {
        Page<Model> page = new Page<>(pageNo, pageSize);

        ModelQuery modelQuery = repositoryService.createModelQuery();
        if (null != parameterMap) {
            if (null != parameterMap.get("key")) {
                modelQuery.modelKey(parameterMap.get("key").toString());
            }
            if (null != parameterMap.get("category")) {
                modelQuery.modelCategoryLike(parameterMap.get("category").toString());
            }
            if (null != parameterMap.get("name")) {
                modelQuery.modelNameLike(parameterMap.get("name").toString());
            }
            if (null != parameterMap.get("name")) {
                modelQuery.modelNameLike(parameterMap.get("name").toString());
            }
        }
        long count = modelQuery.count();
        List<Model> models = modelQuery.listPage(pageNo - 1, pageSize);
        page.setRecords(models);
        page.setTotal(count);
        return page;
    }

    /**
     * 更新模型
     *
     * @param newModel
     * @return
     */
    public boolean updateModel(NewModel newModel) {
        if (Objects.isNull(newModel) && StringUtils.isBlank(newModel.getId())) {
            throw new JeecgBootException("更新参数异常，操作失败");
        }
        Model model = repositoryService.createModelQuery().modelId(newModel.getId()).singleResult();
        if (Objects.isNull(model)) {
            throw new JeecgBootException("模型不存在，请检查参数是否正常");
        }
        BeanUtils.copyProperties(newModel, model);

        try {
            //保存模型数据
            repositoryService.saveModel(model);
            return true;
        } catch (Exception e) {
            throw new JeecgBootException(e);
        }
    }

    /**
     * 删除模型
     *
     * @param id
     * @return
     */
    public boolean deleteModel(String id) {
        Model model = repositoryService.getModel(id);
        if (Objects.isNull(model)) {
            throw new JeecgBootException("流程模型不存在");
        }
        try {
            repositoryService.deleteModel(id);
            return true;
        } catch (Exception e) {
            throw new JeecgBootException(e);
        }
    }

    /**
     * 加载流程模型json数据
     *
     * @param modelId
     * @return
     */
    public ObjectNode loadProcessJson(String modelId) {
        log.info("流程操作 ActivitiUtil ----> getProcessDefinitionJsonData 开始");

        ObjectNode modelNode = null;

        Model model = repositoryService.getModel(modelId);

        if (model != null) {
            try {
                if (org.apache.commons.lang3.StringUtils.isNotEmpty(model.getMetaInfo())) {
                    modelNode = (ObjectNode) objectMapper.readTree(model.getMetaInfo());
                } else {
                    modelNode = objectMapper.createObjectNode();
                    modelNode.put(ActModelNodeEnum.MODEL_NAME.getContent(), model.getName());
                }
                modelNode.put(ActModelNodeEnum.MODEL_ID.getContent(), model.getId());
                byte[] modelEditorSource = repositoryService.getModelEditorSource(model.getId());
                ObjectNode editorJsonNode = (ObjectNode) objectMapper.readTree(new String(modelEditorSource, StandardCharsets.UTF_8));
                modelNode.putPOJO("model", editorJsonNode);

            } catch (Exception e) {
                log.info("流程操作 ActivitiUtil ----> getProcessDefinitionJsonData 结束");
                log.error("Error creating model JSON", e);
                throw new ActivitiException("Error creating model JSON", e);
            }
        }

        log.info("流程操作 ActivitiUtil ----> getProcessDefinitionJsonData 结束");

        return modelNode;
    }

    /**
     * 通过modelId查询model数据
     *
     * @param id
     * @return
     */
    public Model getModelById(String id) {
        return repositoryService.getModel(id);
    }

    /**
     * 保存流程定义json数据
     *
     * @param modelId  流程模型ID
     * @param name     流程模型名称
     * @param bpmnXml  流程定义JSONXml
     * @param svgXml   流程定义svgXml
     * @param key      流程标示
     * @param category 流程分类
     */
    public void saveProcessDefinitionData(String modelId, String key, String category, String name, String bpmnXml, String svgXml) {

        log.info("流程操作 ActivitiUtil ----> saveProcessDefinitionData 开始");

        try {
            Model model = repositoryService.getModel(modelId);

            model.setName(name);
            model.setVersion(model.getVersion() + 1);
            model.setCategory(category);
            model.setKey(key);
            repositoryService.saveModel(model);

            repositoryService.addModelEditorSource(model.getId(), Objects.requireNonNull(bpmnXml.getBytes(StandardCharsets.UTF_8)));
            repositoryService.addModelEditorSourceExtra(model.getId(), Objects.requireNonNull(svgXml.getBytes(StandardCharsets.UTF_8)));
        } catch (Exception e) {
            log.error("Error saving model", e);
            log.info("流程操作 ActivitiUtil ----> saveProcessDefinitionData 结束");
            throw new JeecgBootException("Error saving model", e);
        }
    }

    /**
     * 获取流程定义数据
     *
     * @param modelId
     */
    public ProcessDefinitionData getProcessDefinitionData(String modelId) {
        ProcessDefinitionData processDefinitionData = new ProcessDefinitionData();

        try {
            //获取模型定义数据
            byte[] modelEditorSource = repositoryService.getModelEditorSource(modelId);
            byte[] modelEditorSourceExtra = repositoryService.getModelEditorSourceExtra(modelId);
            //设置模型定义数据
            processDefinitionData.setModelEditorSource(new String(modelEditorSource, StandardCharsets.UTF_8));
            processDefinitionData.setModelEditorSourceExtra(new String(modelEditorSourceExtra, StandardCharsets.UTF_8));
            return processDefinitionData;
        } catch (Exception e) {
            e.printStackTrace();
            throw new JeecgBootException(e);
        }
    }

    /**
     * 部署流程
     *
     * @param modelId
     */
    public void deployProcess(String modelId) {
        try {
            log.info("流程操作 ActivitiUtil ----> devModelById 开始");
            Model model = this.getModelById(modelId);
            if (Objects.isNull(model)) {
                throw new JeecgBootException("部署失败，流程模型不存在");
            }
            //判断流程是否已经部署，如果已经部署，则删除原有流程部署重新进行部署
            if (null != model.getDeploymentId()) {
                repositoryService.deleteDeployment(model.getDeploymentId());
            }
            byte[] bytes = repositoryService.getModelEditorSource(model.getId());
            if (bytes == null) {
                throw new JeecgBootException("请先设计流程定义并成功保存，再进行部署");
            }
//            JsonNode modelNode = null;
//            modelNode = new ObjectMapper().readTree(new String(bytes, StandardCharsets.UTF_8));
//            BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(modelNode);
            if (bytes.length == 0) {
                throw new JeecgBootException("流程定义不符要求，请至少设计一条主线流程");
            }
//            byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(bpmnModel);
            //发布流程
            String processName = model.getKey() + ".bpmn20.xml";
            Deployment deployment = repositoryService.createDeployment()
                    .name(model.getName())
                    .key(model.getKey())
                    .category(model.getCategory())
                    .addBytes(processName, bytes)
                    .enableDuplicateFiltering() // 在部署时会检测已部署的相同文件的最后一条记录，如果内容相同，则不会部署
                    .deploy();//进行部署
            //重新获取数据库最新数据， 避免版本不一致导致报错
            model = this.getModelById(modelId);
            model.setDeploymentId(deployment.getId());
            repositoryService.saveModel(model);

            //获取备注描述
            JSONObject metaInfoMap = JSON.parseObject(model.getMetaInfo());
            // 设置流程分类 保存扩展流程至数据库
            List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().deploymentId(deployment.getId()).list();
            for (ProcessDefinition pd : list) {
                ActZprocess actZprocess = new ActZprocess();
                actZprocess.setId(pd.getId());
                actZprocess.setName(model.getName());
                actZprocess.setProcessKey(model.getKey());
                actZprocess.setDeploymentId(deployment.getId());
                actZprocess.setDescription(metaInfoMap.getString(ModelDataJsonConstants.MODEL_DESCRIPTION));
                actZprocess.setVersion(pd.getVersion());
                actZprocess.setDiagramName(pd.getDiagramResourceName());
                actZprocessService.setAllOldByProcessKey(model.getKey());
                actZprocess.setLatest(true);
                actZprocessService.save(actZprocess);
            }

            log.info("流程操作 ActivitiUtil ----> devModelById 结束");
        } catch (Exception e) {
            log.info("流程操作 ActivitiUtil ----> devModelById 结束");
            log.error("流程部署失败：error{}", e);
            throw new JeecgBootException(e);
        }
    }
}
